package edu.hawaii.halealohacli.command;

import java.util.ArrayList;
import java.util.List;
import javax.xml.datatype.XMLGregorianCalendar;
import org.wattdepot.client.BadXmlException;
import org.wattdepot.client.MiscClientException;
import org.wattdepot.client.ResourceNotFoundException;
import org.wattdepot.client.WattDepotClient;
import org.wattdepot.resource.source.jaxb.Source;

/**
 * Prints the baseline values for a given source.
 * 
 * @author Branden Ogata
 *
 */

public class SetBaseline implements Command {

  /**
   * A list of Baseline instances, stored for eventual calls from monitor-goal.
   * 
   */
  
  private List<Baseline> values = new ArrayList<Baseline>();
  
  /**
   * Outputs the results of the set-baseline command.
   * This also handles saving said results for later access.
   * 
   * @param client  The WattDepotClient to connect to.
   * @param source  The source to obtain baseline values for.
   * @param days  The date on which to obtain baseline values.
   * 
   * @throws  Exception Handles any exceptions that do occur.
   */
  
  @Override
  public void printResults(WattDepotClient client, Source source, XMLGregorianCalendar... days)
      throws Exception {
    if (client.isHealthy()) {
      try {
        System.out.println("Connected to " + client.getWattDepotUri());
        String sourceName = source.getName();
        
        // The temporary baseline storage
        double[] baselines = new double[24];
        
        // Start and end times
        XMLGregorianCalendar startTime = client.getLatestSensorData(sourceName).getTimestamp();
        XMLGregorianCalendar endTime = client.getLatestSensorData(sourceName).getTimestamp();
        
        startTime.setYear(days[0].getYear());
        startTime.setMonth(days[0].getMonth());
        startTime.setDay(days[0].getDay());
        startTime.setHour(0);
        startTime.setMinute(0);
        startTime.setSecond(0);
        startTime.setMillisecond(0);
        
        endTime.setYear(days[0].getYear());
        endTime.setMonth(days[0].getMonth());
        endTime.setDay(days[0].getDay());
        endTime.setHour(1);
        endTime.setMinute(0);
        endTime.setSecond(0);
        endTime.setMillisecond(0);
        
        // Set the initial baseline value
        baselines[startTime.getHour()] = client.getEnergyConsumed(sourceName, startTime, 
                                                                  endTime, 0) / 1000;
        
        // Until twenty-four hours have been measured
        while (endTime.getHour() < 24) {
          // Update the start and end times
          // It would work better to put these updates at the end of the while loop,
          // but since an exception is thrown if the hour values go out of bounds
          // the last hour update must come before obtaining the last value.
          startTime.setHour(endTime.getHour());
          endTime.setHour(endTime.getHour() + 1);
          
          // Set the appropriate baseline value
          baselines[startTime.getHour()] = client.getEnergyConsumed(sourceName, startTime, 
                                                                    endTime, 0) / 1000;
        }
        
        // Prepares a nicely formatted date
        String date = days[0].getYear() + "-" + days[0].getMonth() + "-" + days[0].getDay();
  
        // If a Baseline with the same source already exists, then remove it from the list
        boolean exists = false;
        int currentSource = 0;
        
        while ((!exists) && (currentSource < this.values.size())) {
          if (this.values.get(currentSource).getSource().equals(sourceName)) {
            exists = true;
            this.values.remove(currentSource);
          }
          // Need to increment if it is not the same source
          // If it is the same source, then removing it would mean that the "next"
          // source is now currentSource, so incrementing would not be necessary
          else {
            currentSource++;
          }
        }
        
        // Create and save new Baseline object
        this.values.add(new Baseline(sourceName, date, baselines));
        
        // Indicate success
        System.out.format("Baseline values set for %s on %tD%n", 
                            sourceName, days[0].toGregorianCalendar());
      }
      catch (ResourceNotFoundException e) {
        System.out.println("The source " + source.getName() + " does not exist.");
      }
      catch (BadXmlException e) {
        System.out.println("There was an error in the XML obtained from the server.");
      }
      catch (MiscClientException e) {
        System.out.println("There was an error with the server, possibly a timeout.");
      }
    }
    else {
      System.out.format("Could not connect to: %s%n", client);
    }    
  }

  /**
   * Gets the baseline data for a particular source.
   * 
   * @param name  The name of the source to search for.
   * 
   * @return  data  If the given source name exists in the list of baseline values, 
   *                then return the baseline values for that source.
   *                Else return an array filled with -1.
   */
  
  public double[] getBaselineData(String name) {
    double[] data = new double[24];
    
    // Initializes data to have all values -1
    for (int i = 0; i < 24; i++) {
      data[i] = -1.0;
    }
    
    // If a source with the given name exists, then get the data from that source
    // This is a linear search at the moment, but may be upgraded to a binary search
    // if binary storage is also implemented in the printResults method.
    for (Baseline currentBaseline : this.values) {
      if (currentBaseline.getSource().equals(name)) {
        for (int j = 0; j < 24; j++) {
          data[j] = currentBaseline.getValues()[j];
        }
      }
    }
    
    return data;
  }
  
  /**
   * Gets the Baseline instance for a particular source.
   * 
   * @param name  The name of the source to search for.
   * 
   * @return  baseline  The Baseline instance that corresponds to the given source name.
   *                    If no such source exists, return null.
   * 
   */
  
  public Baseline getBaseline(String name) {
    Baseline baseline = null;
    
    // If a source with the given name exists, then set baseline
    for (Baseline currentBaseline : this.values) {
      if (currentBaseline.getSource().equals(name)) {
        baseline = currentBaseline;
      }
    }
    
    return baseline;
  }
}
